js = {};
js.event = {};
js.win   = {};
js.form = {};
js.ajax = {};
js.regex = {};
js.regex.space = /\s+/;

/**
 * js.include
 *
 * @param string src
 * @param boolean refresh
 */
js.include = function(src,refresh) {
    var id = src.replace(/[^a-zA-Z0-9_-]/g,'')+'-js-inc';
    refresh = typeof(refresh) != 'undefined' ? refresh : false;

    if(js.$(id)) {
        if(refresh) {
            old = js.$(id);
            old.parentNode.removeChild(old);
            delete old;
        }
        else {
            return; // Already created script element
        }
    }

    var head = document.getElementsByTagName("head")[0];
    var script = document.createElement('script');
    script.id = id;
    script.type = 'text/javascript';
    script.src = src;
    head.appendChild(script);
}


/**
 * js.$
 *
 * Just because...
 *
 * @param string e
 * @return object dom element
 */
js.$ = function() {
    return 'string' == typeof arguments[0] ? document.getElementById(arguments[0]) : arguments[0];
}

/**
 * js.inArray
 *
 * Look for the needle in the haystack
 *
 * @param string needle
 * @param array  haystack
 * @return boolean
 */
js.inArray = function(needle, haystack) {
	for(var i=0; i < haystack.length; i++) {
        if(haystack[i] === needle)
	        return true;
	}
	return false;
};

/**
 * js.insertAfter
 *
 * Compliment the DOM method insertBefore
 *
 * @param string|object e
 * @param string|object target
 */
js.insertAfter = function(e, target) {
    var e      = js.$(e);
    var target = js.$(target);
    parent = target.parentNode;
    parent.insertBefore(e, target.nextSibling);
}

/**
 * js.getElementsByClassName
 *
 * Return element objects with a specific classname. Look in tag under
 * specific node. Narrow the search as much as possible by supplying
 * a node and tag. This will increase performance, especially in IE.
 *
 * @param string classname
 * @param element node
 * @param string tag
 * @return array
 */
js.getElementsByClassName = function(searchClass, node, tag){
    var classElements=[];
    node = node || document;
    var els = node.getElementsByTagName(tag|| '*');

    for(var i=0;i<els.length;i++){
        if(js.hasClassName(els[i],searchClass)) {
            classElements.push(els[i]);
        }
    }

    return classElements;
}


/**
 * js.hasClassName
 *
 * @param object|string
 * @return boolean
 */
js.hasClassName = function(e, classname) {
    return js.inArray(classname, js.getClassNames(e));
}

/**
 * js.getClassNames
 *
 * @param object|string element
 * @return array
 */
js.getClassNames = function(e) {
    return js.$(e).className.split(js.regex.space);
}

/**
 * js.addClassName
 *
 * @param object|string element
 * @param string classname
 */
js.addClassName = function(e, classname) {
    var e = js.$(e);
    if(js.hasClassName(e, classname)) return; // Already has classname
    if(e.className)
        classname = " " + classname; // Append - put some space before classname

    e.className += classname; // Now add.
}

/**
 * js.removeClassName
 *
 * @param object|string element
 * @param string classname
 */
js.removeClassName = function(e, classname) {
    var e                 = js.$(e);
    var oldClassNames     = js.getClassNames(e);
    var revisedClassNames = new Array();

    // Delete className temporarily.
    e.className = '';

    for(var i=0; i < oldClassNames.length; i++) {
        if(oldClassNames[i] == classname)
            continue;

        js.addClassName(e, oldClassNames[i]);
    }
}

/**
 * js.event.attach
 *
 * Here is a an example.
 *
 * js.event.attach(getElementById('myDiv'), {
 *     onclick : function(e) {
 *         alert('You clicked?');
 *     },
 *
 *     onkeydown: function(e) {
 *         alert('You pressed?');
 *     }
 * });
 *
 * The context object should have a function named after an event like
 * onclick to make something happen.
 *
 * The 'this' in the onclick function will refer to the anonymous object.
 * A property, this._currentTarget, will be set as a reference to the
 * 'myDiv' html element object.
 *
 * @param object|string element
 * @param object context
 */
js.event.attach = function(element, context) {
   var element      = js.$(element);
   var event_type   = (arguments[2]) ? arguments[2] : false;
   var regex_e_type = /^on(.+)$/;

   // Doesn't exist - so don't attach anything.
   if(!element)
       return;

   // Recursively call attach for each event method handler found
   // in the context object.
   if(!event_type) {
       // Let's let the context object in on the secret :-)
       context._currentTarget = element;

       for(var i in context) {
           if( 'function' == typeof context[i] && (e_type = i.match(regex_e_type)) )
               js.event.attach(element, context, e_type[1]);
       }
   }
   else {
        var bind = function(context) {
            var handler = function(e) {
                var event = window.event ? window.event : e;
                return context["on"+event_type].apply(context, [event]);
            };

            return handler;
        };

        if (element.addEventListener) // DOM method
            element.addEventListener(event_type, bind(context), false);
        else // IE
            element.attachEvent("on" + event_type, bind(context));
    }
};

/**
 * js.event.stop
 *
 * Stop an event from functioning as expected. For example, have
 * a link trigger a function via onclick, but DON'T follow the actual
 * href (if any :-).
 *
 * <a id="some_link" href="somwhere.com">Example</a>
 *
 * js.event.attach(document.getElementById('some_link'), {
 *     onclick : function(e) {
 *                   alert('You clicked?');
 *                   js.event.stop(e);
 *               }
 * });
 *
 * @param Event e
 */
js.event.stop = function(e) {
    if(document.addEventListener)
        e.preventDefault();
    else
        e.returnValue = false;
}

/**
 * js.onLoadReady
 *
 * Simply call js.onLoadReady like
 *
 *   js.onLoadReady(init);
 *   js.onLoadReady(someFunc);
 *
 * @param function x
 */
js.onLoadReady = function(x) {
    if("function" == typeof x) {
        js.onLoadReady.func.push(x);
    }
    else {
        for(var i=0;i<js.onLoadReady.func.length;i++) {
            try { js.onLoadReady.func[i](); }
            catch(e) {}
        }
    }
}

// Remember what functions to execute during js.onLoadReady
js.onLoadReady.func = Array();

// Attach js.onLoadReady to window onload event.
js.event.attach(window, { onload : js.onLoadReady });

/**
 * js.getStyle
 *
 * Retrieve the value of a css property, like the red in
 * color: red;
 *
 * @param element $element
 * @param string  $style for example, backgroundColor
 * @return string
 */
js.getStyle = function(element, style) {
    if(js.$(element).currentStyle) {
        return element.currentStyle[style];
    }
    else if(window.getComputedStyle) {
        return window.getComputedStyle(js.$(element),null).getPropertyValue(style);
    }
}

/**
 * js.win.getViewportSize
 *
 * This function will figure out width and height of the
 * viewport
 *
 * @return object
 */
js.win.getViewportSize = function() {
    var viewportwidth;
    var viewportheight;

    // standards compliant browsers
    if (typeof window.innerWidth != 'undefined') {
        viewportwidth  = window.innerWidth,
        viewportheight = window.innerHeight
    }
    // IE6
    else if (typeof document.documentElement != 'undefined'
        && typeof document.documentElement.clientWidth !=
        'undefined' && document.documentElement.clientWidth != 0)
    {
        viewportwidth  = document.documentElement.clientWidth,
        viewportheight = document.documentElement.clientHeight
    }
    // older versions of IE
    else {
        viewportwidth  = document.getElementsByTagName('body')[0].clientWidth,
        viewportheight = document.getElementsByTagName('body')[0].clientHeight
    }

    return {'width':viewportwidth,'height':viewportheight};
}



/**
 * js.form.disableSubmit
 *
 * This function will attach an onsubmit event to a form to disable
 * the submit button. js.form.disableSubmit('form-id','Please wait...');
 *
 * @param object|string form
 * @param string button_txt
 */
js.form.disableSubmit = function() {
    var form       = js.$(arguments[0]);
    var button_txt = arguments[1] ? arguments[1] : '';

    if(form == null)
        return;

    js.event.attach(form, {
        'onsubmit' : function(e) {
            widget = this._currentTarget.getElementsByTagName("input");

            for(var k=0;k<widget.length;k++) {
                if('submit' == widget[k].type) {
                    widget[k].disabled = true;

                    if(button_txt)
                        widget[k].value = button_txt;
                }
            }
        }
    });
};

/**
 * js.form.disableElements
 *
 * Disable a form for client review. Call it like
 * js.form.disableElements( 'myFormID', {} );
 *
 * The possible options are:
 *
 *    {Xradio      : '/path/checked_radio_img_url.gif'|false,
 *     Oradio      : '/path/radio_img_url.png'|false,
 *     Xcheckbox   : '/path/Xcheckbox.gif'|false,
 *     Ocheckbox   : '/path/Ocheckbox.gif'|false,
 *     ShowOptions : 'text'|'value'}
 *
 * The 'X' at the beginning of radio and checkbox are for those that have
 * checked="checked". If an img src is provided for (X|O)radio or
 * (X|O)checkbox then those radio and checkbox buttons will be replaced:
 *
 * <div class="Xradio"><img src="checked_radio_img_url.gif" /></div>
 * <div class="Oradio"><img src="radio_img_url.png" /></div>
 *
 * Setting (X|O)radio or (X|O)checkbox to false will set the radio button
 * to display none. Then, for example, div.Xradio can have a background image.
 *
 * Without (X|O)radio or (X|O)checkbox options, the widgets will be disabled.
 *
 * If a label has an id containing the radio/checkbox id then it will have
 * an added className called Xradio, Oradio, Xcheckbox, or Ocheckbox. The
 * label can then be styled like label.Xradio etc.
 *
 * textarea, text, select will be replaced as:
 *
 * <div class="textarea"><textarea>blah</textarea></div>
 * replaced as <div class="textarea"><div class="Xtextarea">blah</div></div>
 *
 * <label>Last name: <input type="text" name="lname" value="" />
 * replaced as <label>Last Name: <div class="Otext"></div>
 *
 * <select><option selected="selected" value="Sugar">Sweet</option><select>
 * replaced as <div class="Xselected"><div>Sugar</div></div>
 *
 * Style class attributes: (X|O)textarea, (X|O)text, or Xselected. So,
 * Otextarea is empty (/^\s*$/m) and Xtext is not empty. 0=empty, X=not empty.
 *
 * The 'ShowOptions' option will decide whether to display the <options>
 * value or text. By default it will be options[i].text.
 *
 * @param object|string form
 * @param object options
 */
js.form.disableElements = function() {
    var form = js.$(arguments[0]);
    if(form == null)
        return;

    var params = {'ShowOptions':'text'};
    if(arguments[1]) { for(x in arguments[1]) { params[x] = arguments[1][x] } }

    var re_empty = /^$/m;

    // Replace radio and checkbox with IMG or disable.
    replace_radio = function(old_el) {
        if('radio' == old_el.type)
            classname_add = old_el.checked ? 'Xradio' : 'Oradio';
        else if('checkbox' == old_el.type)
            classname_add = old_el.checked ? 'Xcheckbox' : 'Ocheckbox';
        else
            return;

        labels = form.getElementsByTagName("label");
        for(var y=0; y<labels.length; y++) {
            var label_re = new RegExp(old_el.id);

            if( label_re.test(labels[y].id) )
                js.addClassName(labels[y],classname_add);
        }

        // Just disable. Return.
        if(! (classname_add in params) ) {
            old_el.disabled = true;
            return;
        }

        var div_wrap = document.createElement("div");
        js.addClassName(div_wrap,classname_add);

        // Put an image element in the div wrap.
        if(params[classname_add]) {
            var image = document.createElement("img");
            image.src = params[classname_add];
            div_wrap.appendChild(image);
        }

        // Replace radio button/checkbox with div_wrap.
        old_el.parentNode.insertBefore(div_wrap, old_el);
        old_el.style.display = 'none';
    };

    // Replace textarea and text input.
    replace_text = function (old_el, classname) {
        var div = document.createElement('div');
        js.addClassName( div, re_empty.test(old_el.value.replace(/[\s\r\n]/g,'')) ? 'O'+classname : 'X'+classname );
        div.innerHTML = old_el.value.replace(/([^\s]{32})/g,'$1 ').replace(/\n/g, '<br />\n'); // break long words and add <br /> tags
        old_el.parentNode.insertBefore(div, old_el);
        old_el.style.display = 'none';
    }

    // Loop through input elements.
    var inputs = form.getElementsByTagName("input");
    for(var i=0; i<inputs.length; i++) {
        if('text' == inputs[i].type)
            replace_text(inputs[i],'text');
        else if('submit' == inputs[i].type || 'button' == inputs[i].type || 'reset' == inputs[i].type || 'file' == inputs[i].type)
            inputs[i].style.display = 'none';
        else
            replace_radio(inputs[i]);
    }

    // Loop through textareas.
    var textareas = form.getElementsByTagName("textarea");
    for(var j=0; j<textareas.length; j++) {
        replace_text(textareas[j],'textarea');
    }

    // Loop through select boxes.
    var selects = form.getElementsByTagName("select");
    for(var k=0; k<selects.length; k++) {
        selectedDiv = new Array();
        for(var x=0; x<selects[k].options.length; x++) {
            if(selects[k].options[x].selected) {
                var div = document.createElement('div');
                var selectedValue = document.createTextNode(selects[k].options[x][params.ShowOptions]);
                div.appendChild(selectedValue)
                selectedDiv.push(div);
            }
        }

        if(selectedDiv.length > 0) {
            var divXselected = document.createElement('div');
            divXselected.className = 'Xselected';
            for(m=0; m < selectedDiv.length; m++) {
                divXselected.appendChild(selectedDiv[m]);
            }

            // Replace select dropdown
            selects[k].parentNode.insertBefore(divXselected, selects[k]);
            selects[k].style.display = 'none';
        }
    }
};

/**
 * js.form.counter
 *
 * Example:
 *
 * js.form.counter(1000,'textarea-id','div-counter-id',
 *                 'You entered #count chars, #left of #limit');
 *
 * The rendered template will be placed in the div-counter-id.
 *
 * @param integer limit
 * @param string textinput
 * @param string div
 * @param string tmpl
 */
js.form.counter = function() {
    var div, text, limit, left, count, tmpl;

    limit     = arguments[0];
    textinput = js.$(arguments[1]);
    div       = js.$(arguments[2]);
    tmpl      = arguments[3] ? arguments[3] : '#count entered, #left of #limit';

    if(!(textinput && div))
        return;

    // Add class="counter"
    js.addClassName(div,'counter');

     while(div.hasChildNodes()){
         div.removeChild(div.lastChild);
     }

    var template = function(count, limit, left, tmpl2) {
        return tmpl2.replace('#count',count).replace('#limit',limit).replace('#left',left);
    }

    // Initial update during onload.
    str = textinput.value;
    str = str.replace(/\r/g,'');
    left = limit - str.length;
    text = document.createTextNode(template(str.length,limit,left,tmpl));
    div.appendChild(text);

    // Use for onclick and onkeyup
    var update = function(e) {
        str = this._currentTarget.value;
        str = str.replace(/\r/g,'');
        left = this.limit - str.length;
        this.textNode.data = template(str.length, this.limit, left, this.tmpl);
    }

    js.event.attach(textinput,{
        'tmpl'     : tmpl,
        'limit'    : limit,
        'textNode' : text,
        onclick  : update,
        onkeyup  : update
    });
}

/**
 * js.ajax.request
 *
 * Create an XMLHttpRequest object.
 *
 * @return XMLHttpRequest
 */
js.ajax.request = function() {
    if( !window.XMLHttpRequest ) XMLHttpRequest = function(){
        try{ return new ActiveXObject("MSXML3.XMLHTTP") }catch(e){}
        try{ return new ActiveXObject("MSXML2.XMLHTTP.3.0") }catch(e){}
        try{ return new ActiveXObject("Msxml2.XMLHTTP") }catch(e){}
        try{ return new ActiveXObject("Microsoft.XMLHTTP") }catch(e){}
        throw new Error("Could not find an XMLHttpRequest alternative.")
    };

    return new XMLHttpRequest();
};

/**
 * js.ajax.post
 *
 * Post a request. The callbackFunction will handle the response object.
 *
 * @param string url
 * @param object data
 * @param function callbackFunction
 */
js.ajax.post = function(url, data, callbackFunction){
    var request = js.ajax.request();

    request.open("POST", url, true);
    request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");

    request.onreadystatechange = function(){
        if (request.readyState == 4 && request.status == 200) {
            if (request.responseText){
                callbackFunction(request,data);
            }
        }
    };

    request.send(js.ajax.encode(data));
};

/**
 * js.ajax.get
 *
 * Get a request. The callbackFunction will handle the response object.
 *
 * @param string url
 * @param object data
 * @param function callbackFunction
 */
js.ajax.get = function(url, data, callbackFunction){
    var request = js.ajax.request();

    request.open("GET", url+'?'+js.ajax.encode(data));

    request.onreadystatechange = function(){
        if (request.readyState == 4 && request.status == 200) {
            if (request.responseText){
                callbackFunction(request,data);
            }
        }
    };

    request.send(null);
};

/**
 * js.ajax.encode
 *
 * URI encode the data object then return it as a query string.
 *
 * @param object data
 * @return string
 */
js.ajax.encode = function(data)
{
    var pairs = [];
    var regex = /%20/g;

    for(var name in data) {
        var value = data[name].toString();
        var pair = encodeURIComponent(name).replace(regex,"+") + '=' + encodeURIComponent(value).replace(regex,"+");
        pairs.push(pair);
    }

    if(pairs.length > 0)
        return pairs.join('&');
    else
        return '';
}
